﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Netbits.Common.Threading;

namespace Netbits.Common.Collections
{
	/// <summary>
	/// Represent a locked-keyed-collection based on one collection and one dictionary.
	/// </summary>
	/// <typeparam name="TKey">Key for the item</typeparam>
	/// <typeparam name="TItem">The item</typeparam>
	public class LockedKeyedCollection<TKey, TItem> : IEnumerable<TItem>
	{
		#region Private Variables

		readonly Lock _Lock;
		readonly int _DictionaryCreationThreshold;
		readonly Func<TItem, TKey> _GetKeyForItem;
		List<TItem> _Collection;
		Dictionary<TKey, TItem> _Dictionary;
		IEqualityComparer<TKey> _Comparer;

		#endregion

		#region Constructor

		/// <summary>
		/// Create a new instance of the LockedKeyedCollection
		/// </summary>
		/// <param name="GetKeyForItem_">Function for get the key of an item</param>
		public LockedKeyedCollection( Func<TItem, TKey> GetKeyForItem_ )
			: this( GetKeyForItem_, null, 0, new Lock() )
		{
		}

		/// <summary>
		/// Create a new instance of the LockedKeyedCollection
		/// </summary>
		/// <param name="GetKeyForItem_">Function for get the key of an item</param>
		/// <param name="Lock_">Instance of the lock handler</param>
		public LockedKeyedCollection( Func<TItem, TKey> GetKeyForItem_, Lock Lock_ )
			: this( GetKeyForItem_, null, 0, Lock_ )
		{
		}

		/// <summary>
		/// Create a new instance of the LockedKeyedCollection
		/// </summary>
		/// <param name="GetKeyForItem_">Function for get the key of an item</param>
		/// <param name="Comparer_">Specified equality comparer</param>
		public LockedKeyedCollection( Func<TItem, TKey> GetKeyForItem_, IEqualityComparer<TKey> Comparer_ )
			: this( GetKeyForItem_, Comparer_, 0, new Lock() )
		{
		}

		/// <summary>
		/// Create a new instance of the LockedKeyedCollection
		/// </summary>
		/// <param name="GetKeyForItem_">Function for get the key of an item</param>
		/// <param name="Comparer_">Specified equality comparer</param>
		/// <param name="Lock_">Instance of the lock handler</param>
		public LockedKeyedCollection( Func<TItem, TKey> GetKeyForItem_, IEqualityComparer<TKey> Comparer_, Lock Lock_ )
			: this( GetKeyForItem_, Comparer_, 0, Lock_ )
		{
		}

		/// <summary>
		/// Create a new instance of the LockedKeyedCollection
		/// </summary>
		/// <param name="GetKeyForItem_">Function to get the key of an item</param>
		/// <param name="Comparer_">Specified equality comparer</param>
		/// <param name="DictionaryCreationThreshold_">Create a lookup-dictionary when the specified threshold is exceeded</param>
		public LockedKeyedCollection( Func<TItem, TKey> GetKeyForItem_, IEqualityComparer<TKey> Comparer_, int DictionaryCreationThreshold_ )
			: this( GetKeyForItem_, Comparer_, DictionaryCreationThreshold_, new Lock() )
		{
		}

		/// <summary>
		/// Create a new instance of the LockedKeyedCollection
		/// </summary>
		/// <param name="GetKeyForItem_">Function to get the key of an item</param>
		/// <param name="Comparer_">Specified equality comparer</param>
		/// <param name="DictionaryCreationThreshold_">Create a lookup-dictionary when the specified threshold is exceeded</param>
		/// <param name="Lock_">Instance of the lock handler</param>
		public LockedKeyedCollection( Func<TItem, TKey> GetKeyForItem_, IEqualityComparer<TKey> Comparer_, int DictionaryCreationThreshold_, Lock Lock_ )
		{
			if( Comparer_ != null )
				_Comparer = Comparer_;
			else
				_Comparer = EqualityComparer<TKey>.Default;

			_GetKeyForItem = GetKeyForItem_;
			_Collection = new List<TItem>();
			_Lock = Lock_;
			_DictionaryCreationThreshold = DictionaryCreationThreshold_;
			if( _DictionaryCreationThreshold == 0 )
				_Dictionary = new Dictionary<TKey, TItem>( _Comparer );
		}

		#endregion

		#region Public Properties

		/// <summary>
		/// Returns the Lock instance
		/// </summary>
		public Lock SyncObject
		{
			get
			{
				return _Lock;
			}
		}

		/// <summary>
		/// Returns the equality comparer that is used to determine equality of keys in the collection
		/// </summary>
		public IEqualityComparer<TKey> Comparer
		{
			get
			{
				return _Comparer;
			}
		}

		/// <summary>
		/// Get the item with the specified key
		/// </summary>
		/// <param name="Key_">The key of the item to get</param>
		/// <returns>The item with the specified key, otherwise default(TItem)</returns>
		public TItem this[TKey Key_]
		{
			get
			{
				try
				{
					_Lock.EnterReadLock();

					if( _Dictionary != null )
					{
						if( _Dictionary.ContainsKey( Key_) )
							return _Dictionary[Key_];
						return default( TItem );
					}

					int idx = _GetIndexOfKey( Key_ );
					if( idx >= 0 )
						return _Collection[idx];

					return default( TItem );
				}
				catch
				{
					return default( TItem );
				}
				finally
				{
					_Lock.ExitReadLock();
				}
			}
		}

		/// <summary>
		/// Get the item with the specified index
		/// </summary>
		/// <param name="Index_">The index of the item to get</param>
		/// <returns>The item with the specified index, otherwise default(TItem)</returns>
		public TItem this[int Index_]
		{
			get
			{
				try
				{
					_Lock.EnterReadLock();
					if( Index_ < 0 || Index_ > _Collection.Count - 1 )
						return default( TItem );

					return _Collection[Index_];
				}
				finally
				{
					_Lock.ExitReadLock();
				}
			}
		}

		/// <summary>
		/// Get the number of items in the keyed-collection.
		/// </summary>
		public int Count
		{
			get
			{
				try
				{
					_Lock.EnterReadLock();
					return _Collection.Count;
				}
				finally
				{
					_Lock.ExitReadLock();
				}
			}
		}

		#endregion

		#region Public Functions

		/// <summary>
		/// Adds the item to the keyed-collection
		/// </summary>
		/// <param name="Item_">The item to be added to the keyed-collection</param>
		public void Add( TItem Item_ )
		{
			if( Item_ == null )
				throw new ArgumentNullException( "Item_" );

			Insert( -1, Item_ );
		}

		/// <summary>
		/// Clear all items from the keyed-collection
		/// </summary>
		public void Clear()
		{
			try
			{
				_Lock.EnterWriteLock();
				_Dictionary.Clear();
				_Collection.Clear();
			}
			finally
			{
				_Lock.ExitWriteLock();
			}
		}

		/// <summary>
		/// Determines whether an item is in the keyed-collection
		/// </summary>
		/// <param name="Item_">The item to locate in the keyed-collection</param>
		/// <returns>true if the item is found in the keyed-collection, otherwise false</returns>
		public bool Contains( TItem Item_ )
		{
			try
			{
				_Lock.EnterReadLock();
				return _Collection.Contains( Item_ );
			}
			finally
			{
				_Lock.ExitReadLock();
			}
		}

		/// <summary>
		/// Determines whether the keyed-collection contains an item with the specified key.
		/// </summary>
		/// <param name="Key_">The key to locate in the keyed-collection</param>
		/// <returns>true if the key is found in the keyed-collection, otherwise false</returns>
		public bool Contains( TKey Key_ )
		{
			try
			{
				_Lock.EnterReadLock();

				if( _Dictionary != null )
					return _Dictionary.ContainsKey( Key_ );

				return _GetIndexOfKey( Key_ ) >= 0;
			}
			finally
			{
				_Lock.ExitReadLock();
			}
		}

		/// <summary>
		/// Copies the items to a compatible one-dimensional array, starting at the specified index of the target array.
		/// </summary>
		/// <param name="Array_">The one-dimensional array</param>
		/// <param name="Index_">The index in array at witch copying begins.</param>
		public void CopyTo( TItem[] Array_, int Index_ )
		{
			try
			{
				_Lock.EnterReadLock();
				int index = Index_;
				foreach( TItem item in _Collection )
				{
					Array_[index++] = item;
					if( Array_.Length == Index_ )
						break;
				}
			}
			finally
			{
				_Lock.ExitReadLock();
			}
		}

		/// <summary>
		/// Returns an locked enumerator that iterates through the keyed-collection
		/// </summary>
		/// <returns>The locked iterator</returns>
		public IEnumerator<TItem> GetEnumerator()
		{
			return new LockedEnumerator<TItem>( _Collection, _Lock );
		}

		/// <summary>
		/// Searches for the specified item and returns the index of the first occurrence within the entire keyed-collection.
		/// </summary>
		/// <param name="Item_">The item to locate in the keyed-collection</param>
		/// <returns>The index of first occurrence of the item, otherwise -1</returns>
		public int IndexOf( TItem Item_ )
		{
			try
			{
				_Lock.EnterReadLock();
				return _Collection.IndexOf( Item_ );
			}
			finally
			{
				_Lock.ExitReadLock();
			}
		}

		/// <summary>
		/// Insert an item to the keyed-collection at the specified index.
		/// </summary>
		/// <param name="Index_">The index at which item should be inserted.</param>
		/// <param name="Item_">The item to insert</param>
		public void Insert( int Index_, TItem Item_ )
		{
			if( Item_ == null )
				throw new ArgumentNullException( "Item_" );

			TKey key = _GetKeyForItem( Item_ );

			try
			{
				_Lock.EnterUpgradeableReadLock();

				if( Index_ < -1 || Index_ > _Collection.Count - 1 )
					throw new ArgumentException( "Index out of range!" );

				int index = Index_;
				if( Index_ == -1 )
				{
					index = _Collection.Count - 1;
					if( index == -1 )
						index = 0;
				}

				if( _Dictionary != null && _Dictionary.ContainsKey( key ) )
					throw new ArgumentException( "An element with the same key already exists in the keyed-collection" );

				if( _Dictionary == null )
				{
					foreach( TItem item in _Collection )
					{
						if( _Comparer.Equals( key, _GetKeyForItem( item ) ) )
							throw new ArgumentException( "An element with the same key already exists in the keyed-collection" );
					}
				}

				try
				{
					_Lock.EnterWriteLock();

					_Collection.Insert( index, Item_ );
					if( _Dictionary != null )
					{
						_Dictionary.Add( key, Item_ );
					}
					else if( _DictionaryCreationThreshold != -1 && _Collection.Count > _DictionaryCreationThreshold )
					{
						_Dictionary = new Dictionary<TKey, TItem>( _Comparer );
						foreach( TItem item in _Collection )
							_Dictionary.Add( _GetKeyForItem( item ), item );
					}
				}
				finally
				{
					_Lock.ExitWriteLock();
				}
			}
			finally
			{
				_Lock.ExitUpgradeableReadLock();
			}
		}

		/// <summary>
		/// Remove the specified item from the keyed-collection
		/// </summary>
		/// <param name="Item_">The item to remove from the keyed-collection</param>
		/// <returns>true if the item was successfully removed, otherwise false</returns>
		public bool Remove( TItem Item_ )
		{
			try
			{
				_Lock.EnterWriteLock();

				_Collection.Remove( Item_ );
				if( _Dictionary != null )
					_Dictionary.Remove( _GetKeyForItem( Item_ ) );

				return true;
			}
			catch
			{
				return false;
			}
			finally
			{
				_Lock.ExitWriteLock();
			}
		}

		/// <summary>
		/// Remove the specified key from the keyed-collection.
		/// </summary>
		/// <param name="Key_">The key to remove from the keyed-collection</param>
		/// <returns>true if the key was successfully removed, otherwise false</returns>
		public bool Remove( TKey Key_ )
		{
			try
			{
				_Lock.EnterWriteLock();

				if( _Dictionary != null )
					_Dictionary.Remove( Key_ );

				int index = _GetIndexOfKey( Key_ );
				_Collection.RemoveAt( index );
				return true;
			}
			catch
			{
				return false;
			}
			finally
			{
				_Lock.ExitWriteLock();
			}
		}

		/// <summary>
		/// Remove the item with the specified index of the keyed-collection
		/// </summary>
		/// <param name="Index_">The index of the item to remove.</param>
		public void RemoveAt( int Index_ )
		{
			try
			{
				_Lock.EnterUpgradeableReadLock();
				if( Index_ < 0 || Index_ > _Collection.Count - 1 )
					return;

				if( _Dictionary != null )
				{
					TItem item = _Collection[Index_];
					_Dictionary.Remove( _GetKeyForItem( item ) );
				}

				_Collection.RemoveAt( Index_ );
			}
			finally
			{
				_Lock.ExitUpgradeableReadLock();
			}
		}

		/// <summary>
		/// Sort the keyed-collection
		/// </summary>
		public void Sort()
		{
			_Collection.Sort();
		}

		#endregion

		#region Private Functions

		int _GetIndexOfKey( TKey Key_ )
		{
			TItem item;
			if( _Dictionary != null )
			{
				if( _Dictionary.TryGetValue( Key_, out item ) )
					return _Collection.IndexOf( item );
			}

			for( int i = _Collection.Count - 1; i >= 0; i-- )
			{
				TKey key = _GetKeyForItem( _Collection[i] );
				if( _Comparer.Equals( key, Key_ ) )
					return i;
			}
			return -1;
		}

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return new LockedEnumerator<TItem>( this, _Lock );
		}

		#endregion
	}
}
